
Models.CheltuialaBase = Models.Form.extend(
{
    _componentSelectors: {
        criteriiContainer:  '.criteriu-forms-container',
        target:             '.target'
    },

    _fields: {
        target:     'target_id'
    },

    _messages: {
        addConfirmation: 'Cheltuiala a fost înregistrată cu succes.'
    },

    Criterii: null,
    Target: null,

    _initComponents: function()
    {
        this.Target     = new Models.CheltuialaBase.Target( this.getElement().find(this._componentSelectors.target), { mainObject: this } );
        this.Criterii   = new Models.CheltuialaBase.Criterii( this.getElement().find(this._componentSelectors.criteriiContainer), { mainObject: this } );
    },

    //// define custom handlers: onReset, onBeforeSubmit, onSuccess

    _onReset: function()
    {
        // reset Criterii
        this.Criterii.reset();

        // campul care contine id-urile apartamentelor selectate trebuie resetat deoarece este de tipul 'hidden'
        this.getField( this._fields.target ).val('');

        // resetez apartamentele
        this.Target.reset();
    },

    _onBeforeSubmit: function()
    {
        // se genereaza lista cu apartamentele selectate si se seteaza ca valoare pentru campul 'target_id' al formularului
        this.getField( this._fields.target )
                .val( this.Target.getSelectionAsString() );
    },

    _onSuccess: function(response)
    {
        // afisez un mesaj de confirmare
        App.ConfirmationMessage.show(this._messages.addConfirmation);
    }

});

// move 'beneficiar' model in Models.CheltuialaBase.Criterii
// use self.prototype to access Models.CheltuialaBase.Criterii

Models.CheltuialaBase.Criterii = Module.extend(
{
    _init: function()
    {
        this._mainObject = this.getOption( 'mainObject' );

        var self = this;

        // create instance for each object model
        for ( var criteriuCod in Models.CheltuialaBase.Criterii.Models)
        {
            var criteriuModel = Models.CheltuialaBase.Criterii.Models[criteriuCod];

            // check if there is a defined model
            if ( !criteriuModel ) return;

            var $criteriuForm = self.getForm( criteriuCod );
            self._CriteriuObjects[criteriuCod] = new criteriuModel( $criteriuForm, { mainObject: self._mainObject } );
        }
    },

    // a reference to main object
    _mainObject: null,

    _CriteriuObjects: {},

    /**
    * Returns a criteriu form
    * @param string|int criteriuCod
    */
    getForm: function( criteriuCod )
    {
        var attribute = Type.isNumeric(criteriuCod) ?
                'data-id' :
                'data-cod';

        var $form = this.getElement().children( '[' + attribute + '=' + criteriuCod + ']' );

        return $form;
    },

    /**
    * Returns all criteriu forms
    */
    getForms: function()
    {
        var $forms = this.getElement().children();
        return $forms;
    },

    /**
    * Hide a criteriu form or all forms (if the argument is not specified)
    */
    hideForm: function( criteriuCod )
    {
        if ( typeof criteriuCod != 'undefined' )
        {
            this.getForm(criteriuCod).hide();
        }
        else
        {
            this.getForms().hide();
        }
    },

    showForm: function( criteriuCod )
    {
        var $form = this.getForm( criteriuCod );
        // hide all forms
        this.hideForm();
        // show our form
        $form.show();
    },

    reset: function()
    {
        this.hideForm();

        // reset the form objects
        $.each( this._CriteriuObjects, function(){
            // call 'reset' method, if any
            if ( $.isFunction( this.reset ) )
            {
                this.reset.call( this );
            }
        })
    }

})

Models.CheltuialaBase.Criterii.Models = {}

Models.CheltuialaBase.Criterii.Models.beneficiar = Module.extend(
{
    // elementul care contine textul cu instructiuni
    _$instruction: null,
    // elementul care reprezinta tabelul cu apartamente
    _$table: null,
    // template row
    _$blankRow: null,
    _Target: null,
    _rowCounter: 0,
    _rowCounterPattern: '{row}', // see CriteriuFormBeneficiar::ROW

    _init: function()
    {
        var $element = this.getElement();

        // initializari ale elementelor
        this._$instruction  = $element.children( '.instruction' );
        this._$table        = $element.find( '>table.table>tbody' );
        this._$blankRow     = $element.find( 'tr.field-row.blank' );

        // initially the table is hidden and the instructional text is visible
        this._hideTable();

        this._Target = this.getOption( 'mainObject' ).Target;

    },

    _initTargetChange: function()
    {
        var self = this;
        if ( !self._Target ) return;

        self._Target.bind( self._Target._events.change , function( event, data ){
            if ( data.action == 'checked' )
            {
                self.addApartament({
                    id:         data.apartamentId,
                    number:     data.apartamentNumber,
                    scaraName:  data.scaraName,
                    blocName:   data.blocName
                })
            }
            else
            {
                self.removeApartament( data.apartamentId );
            }
        })
    },

    _initBindRemove: function()
    {
        var self = this;
        self.bind( self._Target._events.remove , function( event, data ) {
            self._Target.unselectApartament( data.apartamentId );
        })
    },

    _initRemoveEvent: function()
    {
        var self = this;
        self._$table.on( 'click', '.apartament-delete', function(){
            var $rowAp = $(this).closest('.field-row');
            self.removeApartament( $rowAp );
            self.trigger( self._Target._events.remove, { apartamentId: $rowAp.attr('data-apartament-id') } );
        })
    },

    /**
    * Adauga un apartament in tabel
    * @param apartament PlainObject: {id, number, scaraId, scaraName, blocId, blocName}
    */
    addApartament: function( apartament )
    {
        var $row = this._createRow( apartament );

        var position = this._findRowPosition( apartament );

        // insert the new row
        this._insertRow( $row, position );

        // hide the instructional text and show the table
        this._showTable();
    },

    /**
    * @param apartamentId - can also indicate the apartament row (as jQuery object)
    *
    * This method doesn't trigger any event
    */
    removeApartament: function( apartamentId )
    {
        var $table = this._$table;

        var scaraSelector = function(){
            return !$(this).attr( 'data-scara-name' ).strcmp( scaraName )
                    && blocSelector.call( this );
        }

        var blocSelector = function(){
            return !$(this).attr( 'data-bloc-name' ).strcmp( blocName );
        }

        // find the row that will be removed
        var $row;
        $row = ( apartamentId instanceof jQuery ) ?
                $table.children().filter( apartamentId ) :
                $table.children( '[data-apartament-id='+apartamentId+']' );

        if ( $row.length == 0 ) return;

        $row.remove();

        var scaraName = $row.attr('data-scara-name'),
            blocName = $row.attr('data-bloc-name');

        // select all rows that contains the same bloc
        var $similarRows = $table.children().filter(blocSelector);
        // if the removed row was the first row in the collection:
        //      add a bloc cell and set the rowspan attribute
        var $firstRow = $similarRows.eq(0),
            $blocCell = $firstRow.find('.bloc');
        if ($blocCell.length == 0)
        {
            $blocCell = $row.find('.bloc');
            $firstRow.prepend($blocCell);
        }
        $blocCell.attr('rowspan', $similarRows.length);

        // select all rows that contains the same scara
        $similarRows = $table.children().filter(scaraSelector);
        // if the removed row was the first row in the collection:
        //      add a scara cell and set the rowspan attribute
        $firstRow = $similarRows.eq(0);
        var $scaraCell = $firstRow.find('.scara');
        if ($scaraCell.length == 0)
        {
            $scaraCell = $row.find('.scara');
            $scaraCell.insertBefore($firstRow.children('.apartament').eq(0));
        }
        $scaraCell.attr('rowspan', $similarRows.length);

        this._checkTableEmptiness();
    },

    reset: function()
    {
        this._$table.empty();
        this._hideTable();
        this._rowCounter = 0;
    },

    _showTable: function()
    {
        this._$instruction.hide();
        this._$table.parent().show();
    },

    _hideTable: function()
    {
        this._$instruction.show();
        this._$table.parent().hide();
    },

    _checkTableEmptiness: function()
    {
        this._$table.children().eq(0).length == 0 ?
            this._hideTable() :
            this._showTable();
    },

    /**
    * Creeaza o linie noua (corespunzatoare unui apartament)
    * @param rowData PlainObject {id, number, scaraId, scaraName, blocId, blocName}
    */
    _createRow: function(rowData)
    {
        var self = this;

        // create a duplicate of the blank row
        var $row = this._$blankRow.clone();
        // change rowCounterTemplate with rowCounter for every field that has a 'name' attribute
        $row.find('[name]').each(function(){
            var $field = $(this);
            var nameAttr = $field.attr('name');
            nameAttr = nameAttr.replace(self._rowCounterPattern, self._rowCounter);
            $field.attr('name', nameAttr);
        })

        // increase the counter
        this._rowCounter++;

        // populate the row with data
        $row.attr('data-apartament-id', rowData.id);
        $row.attr('data-apartament-number', rowData.number);
        $row.attr('data-scara-id', rowData.scaraId);
        $row.attr('data-scara-name', rowData.scaraName);
        $row.attr('data-bloc-id', rowData.blocId);
        $row.attr('data-bloc-name', rowData.blocName);
        $row.children('.bloc').text(rowData.blocName);
        $row.children('.scara').text(rowData.scaraName);
        $row.children('.apartament-number').text(rowData.number);
        $row.find('> .apartament-value > input.id').val(rowData.id);

        return $row;
    },

    /**
    * Finds the position of the new row to be inserted.
    * Returns:
    *      - the row before which the new row will be inserted OR
    *      - false = do not insert the row;
    *      - -1 = insert at the end
    */
    _findRowPosition: function(rowData)
    {
        var $rows       = this._$table.children(),
            position    = -1; // this is the default position

        $rows.each(function(){
            var $row = $(this),
                rowBlocName        = $row.attr('data-bloc-name'),
                rowScaraName       = $row.attr('data-scara-name'),
                rowApartamentNumber  = $row.attr('data-apartament-number'),
                rowApartamentId      = $row.attr('data-apartament-id');

            var blocCompare    = rowData.blocName.strnatcmp(rowBlocName),
                scaraCompare   = rowData.scaraName.strnatcmp(rowScaraName),
                numberCompare   = rowData.number.toString().strnatcmp(rowApartamentNumber);

            // trying to insert an existing row: the bloc name, the scara name and apartament number are the same OR the apartament id is the same
            if ((blocCompare == 0 && scaraCompare == 0 && numberCompare == 0) || rowApartamentId == rowData.id)
            {
                position = false;
                return false;
            }

            if ((blocCompare < 0) ||
                (blocCompare == 0 && scaraCompare < 0) ||
                (blocCompare == 0 && scaraCompare == 0 && numberCompare < 0))
            {
                position = $row;
                return false;
            }
        })

        return position;
    },

    _insertRow: function( $row, position )
    {
        var $table = this._$table;

        // search if the first row contains the same scara as the new one
        // if so, remove the scara cell from that row and increase rowspan value for the scara cell of the new row
        //
        // search if the first row contains the same bloc as the new one
        // if so, remove the bloc cell from that row and increase rowspan value for the bloc cell of the new row

        var scaraName  = $row.attr( 'data-scara-name' ),
            blocName   = $row.attr( 'data-bloc-name' );

        var scaraSelector = function(){
            return !$(this).attr('data-scara-name').strcmp(scaraName) && blocSelector.call(this);
        }

        var blocSelector = function(){
            return !$(this).attr('data-bloc-name').strcmp(blocName);
        }

        if (position == -1)
        {
            // insert to the end
            $table.append($row);
        }
        else if (position instanceof jQuery)
        {
            // insert before the specified element (position)
            $row.insertBefore(position);
        }

        // select all rows that contains the same scara
        var $similarRows = $table.children().filter( scaraSelector );
        // first row must have the rowspan attribute set for the scara cell
        $similarRows.eq(0).find('.scara').attr('rowspan', $similarRows.length);
        // remove the remaining scara cell
        $similarRows.slice(1).children('.scara').eq(0).remove();

        // select all rows that contains the same bloc
        $similarRows = $table.children().filter(blocSelector);
        // first row must have the rowspan attribute set for the bloc cell
        $similarRows.eq(0).find('.bloc').attr('rowspan', $similarRows.length);
        // remove the remaining bloc cell
        $similarRows.slice(1).children('.bloc').eq(0).remove();

    }
});

Models.CheltuialaBase.Target = Module.extend(
{
    _events: {
        remove: 'removeApartament',
        change: 'change'
    },

    _selectors: { // a se vedea apartamente.css
        selected:           '.selected',
        partiallySelected:  '.partial',
        apartamentItem:     '.apartament-item',
        switchButton:       '.switch',
        scaraContainer:     '.scara-item-container',
        apartamentePopup:   '.apartamente-popup',
        switchCollapse:     '.collapse'
    },

    _classes: {
        selected:           'selected',
        partiallySelected:  'partial',
        apartamentItem:     'apartament-item',
        switchButton:       'switch',
        scaraContainer:     'scara-item-container',
        apartamentePopup:   'apartamente-popup',
        switchCollapse:     'collapse'
    },

    _options: {
        selectionSeparator: '-'
    },

    // state order selection: unchecked, partial, checked
    _state: {
        checked: 1,
        partial: 2,
        unchecked: 0,
        toggle: -1 // indicates that the item has only 2 states
    },

    _Items: ['apartament', 'etaj', 'scara', 'bloc'],

    _$apartamentePopup: $(),

    _initApartamentePopup: function()
    {
        var self = this;

        // show/hide apartamente when pressing the up/down arrow button near to each scara
        self.getElement().on( 'click', self._selectors.switchButton, function(){
            var $button = $(this),
                $scaraContainer = $button.closest(self._selectors.scaraContainer),
                $popup = $scaraContainer.find(self._selectors.apartamentePopup);

            if ( $popup.is(':visible') )
            {
                // if the popup is opened, hide it and change button status
                self._closePopup();
            }
            else
            {
                self._openPopup($popup);
            }
        })

        // the popup will be closed if the user clicks elsewhere
        $.documentEvent( [self._selectors.scaraContainer+' '+self._selectors.apartamentePopup, self._selectors.scaraContainer+' '+self._selectors.switchButton], function(){
            self._closePopup();
        })

    },

    _initSelectItems: function()
    {
        var self = this;

        var selector = [];


        $.each(self._Items, function(){
            var item = this.valueOf();
            selector.push( '.' + item + '-item ' );
        })
        selector = selector.join(',');

        self.getElement().on( 'click', selector, function(){
            var $item = $(this);
            self._changeItem( $item, null, true );
        })

    },

    selectApartament: function( id )
    {
        var $apartament = this._getApartamentItem( id );
        this._changeItem( $apartament, this._state.checked );
    },

    unselectApartament: function( id )
    {
        var $apartament = this._getApartamentItem( id );
        this._changeItem( $apartament, this._state.unchecked );
    },

    /**
    * Returneaza un array continand ID-urile apartamentelor selectate
    * @return array
    */
    getSelection: function()
    {
        var selection = [];

        this.getElement().find('.apartament-item.selected').each(function(){
            selection.push($(this).attr('value'));
        });

        return selection;
    },

    getSelectionAsString: function()
    {
        var selection = this.getSelection().join(this._options.selectionSeparator);

        return selection;
    },

    reset: function()
    {
        this._changeState(this.getElement().find('.item.selected'), this._state.unchecked);
        this._closePopup();
    },

    _getApartamentItem: function( id )
    {
        var $apartament = this.getElement().find( this._selectors.apartamentItem+'[value='+id+']' );
        return $apartament;
    },

    _changeItem: function( $item, forceState, triggerEvent )
    {
        var self = this,
            itemIndex;

        $.each( self._Items, function( index ){
            if ( $item.hasClass( this.valueOf() + '-item' ) )
            {
                itemIndex = index;
                return false;
            }
        })

        // change item state
        self._changeState( $item, forceState, triggerEvent );

        // select/unselect all child items (not for apartamente)
        if ( itemIndex )
        {
            // determine the item state
            var state = self._isSelected( $item ) ?
                        self._state.checked :
                        self._state.unchecked,
                $currentItem = $item;

            // the state change should propagate downwards to the apartamente
            for ( var i = itemIndex; i>0; i-- )
            {
                var $nextItems = self[ '_get' + self._Items[i].ucfirst() + self._Items[i-1].ucfirst() + 'Items' ]( $currentItem );
                self._changeState( $nextItems, state, triggerEvent );
                $currentItem = $nextItems;
            }
        }

        // check parent item state
        // the state change should propagate upwards to the bloc
        if (itemIndex < self._Items.length - 1)
        {
            for ( i=itemIndex, $currentItem = $item; i < self._Items.length-1; i++ )
            {
                // retrieve all items on the same hierarchy etaj
                var $siblingItems = self[ '_get' + self._Items[i+1].ucfirst() + self._Items[i].ucfirst() + 'Items' ]( $currentItem ),
                    // determine the selected items (also the items that are partially selected)
                    $selectedItems = $siblingItems.filter( '.selected' ),
                    // the parent item
                    $parentItem = self[ '_get' + self._Items[i+1].ucfirst() + 'Item' ]( $currentItem ),
                    parentState;

                // no sibling is selected -> the parent item goes to unchecked state
                if ( $selectedItems.length == 0 )
                {
                    parentState = self._state.unchecked;
                }
                // all siblings are selected -> the parent item goes to checked state
                else if ( ($selectedItems.filter( '.partial' ).length == 0) && ($selectedItems.length == $siblingItems.length) )
                {
                    parentState = self._state.checked;
                }
                // only some siblings are selected -> the parent item goes to partially-checked state
                else
                {
                    parentState = self._state.partial;
                }

                // change parent item state
                self._changeState( $parentItem, parentState );

                // go to the next hierarchy etaj
                $currentItem = $parentItem;
            }
        }

    },

    /**
    * Change an item state: checked, unchecked, partially-checked
    * @param state - Force the state of an item; if not specified, autodetect it
    */
    _changeState: function( $item, state, triggerEvent ){
        // toggle state if second parameter is not specified
        if ( typeof state == 'undefined' || state === null )
        {
            $item.removeClass('partial');
            state = $item.hasClass('selected') ?
                    this._state.unchecked :
                    this._state.checked;
        }

        switch ( state )
        {
            case this._state.checked:
                $item.removeClass('partial').addClass('selected');
                break;
            case this._state.partial:
                $item.addClass('partial selected');
                break;
            case this._state.unchecked:
                $item.removeClass('selected partial');
                break;
        }

        if ( typeof triggerEvent != 'undefined' && triggerEvent )
        {
            this._changeEvent( $item, state );
        }

    },

    _changeEvent: function( $item )
    {
        // the event is triggered only for apartamente changes
        if (!$item.is('.apartament-item')) return;

        var eventParams,
            self = this;

        if (typeof state == 'undefined')
        {
            var state = $item.hasClass('selected') ? self._state.checked : self._state.unchecked;
        }

        // there can be multiple items changed at once
        $item.each(function(){
            eventParams = self._getApartamentInfo($(this));

            if (state == self._state.unchecked)
            {
                eventParams.action = 'unchecked';
            }
            else
            {
                eventParams.action = 'checked';
            }

            self.trigger( self._events.change, eventParams );

        })
    },

    _getApartamentInfo: function($item)
    {
        return {
            apartamentId:    $item.attr('value'),
            apartamentNumber:$item.text(),
            scaraName:     this._getScaraItem($item).text(),
            blocName:      this._getBlocItem($item).text()
        }
    },

    _isSelected: function($item)
    {
        return $item.is('.selected');
    },

    _getEtajItem: function($item)
    {
        return $item.closest('.etaj-row').find('.etaj-item');
    },

    _getScaraItem: function($item)
    {
        return $item.closest('.scara-item-container').find('.scara-item');
    },

    _getBlocItem: function($item)
    {
        return $item.closest('.bloc-container').find('.bloc-item');
    },

    _getEtajApartamentItems: function($etajItem)
    {
        return $etajItem.closest('.etaj-row').find('.apartament-item');
    },

    _getScaraEtajItems: function($scaraItem)
    {
        return $scaraItem.closest('.scara-item-container').find('.etaj-item');
    },

    _getBlocScaraItems: function($blocItem)
    {
        return $blocItem.closest('.bloc-container').find('.scara-item');
    },

    /**
    * Open the apartamente popup
    * @param $popup
    */
    _openPopup: function($popup)
    {
        // close any opened popup
        this._closePopup();
        // show the popup
        this._$apartamentePopup = $popup.show();
        // change the button state
        $popup.closest(this._selectors.scaraContainer).find(this._selectors.switchButton).addClass(this._classes.switchCollapse);
    },

    /**
    * Close the opened popup
    */
    _closePopup: function()
    {
        // hide the popup
        this._$apartamentePopup.hide();
        // change the button state
        this._$apartamentePopup.closest(this._selectors.scaraContainer).find(this._selectors.switchButton).removeClass(this._classes.switchCollapse);
    }

})
